package ca.usask.cs.decisiontree;

import java.util.Iterator;

import javax.swing.JOptionPane;

import ca.usask.cs.decisiontree.api.DecisionTree;
import ca.usask.cs.decisiontree.api.EventEdgeInterface;
import ca.usask.cs.decisiontree.api.EventNodeInterface;
import ca.usask.cs.decisiontree.api.DecisionNodeInterface;
import ca.usask.cs.decisiontree.api.Edge;
import ca.usask.cs.decisiontree.api.IntermediateTreeNode;
import ca.usask.cs.decisiontree.api.Node;
import ca.usask.cs.exceptions.Exceptions;

/**
 * A DecisionTree represents a decision tree in its entirety. 
 * A DecisionTree contains TreeNodes connected by Edges, and has a root node to start from.
 * 
 * @invariant root node is not null
 *
 * @author Alan Yeung
 * @reviewer Travis
 * @editor Wenyi An
 * @QA Tony
 * 
 */
public class BasicDecisionTree implements DecisionTree, Cloneable {

    private Node rootNode;  //rootNode of this Tree
    private String treeName;
    private int treeID;
    private double rVal;
    private Exceptions exception;
     
    /**
     * Constructor for BasicEdge
     * 
     * @param node Root Node for TreeNode, cannot be null
     */
    public BasicDecisionTree (Node newRootNode, int newTreeID)  throws IllegalArgumentException
    {
    	exception = new Exceptions();
    	try{
    		exception.nullNodeCheck(newRootNode, "Root Node of a BasicDecisionTree cannot be null");
    	}
    	catch(IllegalArgumentException e){
    		System.out.println(e.getMessage());
    	}
        this.rootNode = newRootNode;
        this.treeID = newTreeID;
        this.treeName = null;
    }
    
    /**
     * retrieves the Tree name
     * @return tree name in string format
     */
    public String getTreeName()
    {
        return this.treeName;
    }

    /**
     * Assigns the tree with a new name
     * @param theTreeName in string
     */
    public void setTreeName(String theTreeName)
    {
        this.treeName = theTreeName;
    }

    /**
     * retrieves the tree id
     * @return tree id in integer
     */
    public int getTreeID()
    {
        return this.treeID;
    }

    /**
     * Checks if the DecisionTree is a valid Decision Tree by ensuring each leaf node
     * is a termina Node;
     *      Use validRecurseHelper on rootNode
     *  @return True if Valid
     *          False if inValid
     *  @todo
     */
    public boolean isValidTree()
    {
        return validRecurseHelper(this.rootNode);
    }

    /**
     * Recursive method that checks if the subTree of the curNode is a valid 
     * decisionTree
     *      BaseCase:       if the node is terminal Node -> true
     *              :       if the node is an intermediateNode and has no children -> false
     *      Recursive Step: For each Child recurse with the child.
     *                      Return the sum of the child solutions;
     * @param curNode
     * @return
     */
    protected boolean validRecurseHelper(Node theCurNode)
    {
        // if the node is a terminalNode must be true
        if(theCurNode instanceof TerminalNode)
        {
            return true;
        }
        // if it is not a terminalNode must be intermediatTreeNode
        else
        {
            // cast curNode to IntermediatTreeNode
            IntermediateTreeNode curNode = (IntermediateTreeNode) theCurNode;
            Iterator<Edge> edgeList = curNode.getChildEdgeList();

            // if the node is an intermediateTreeNode and has no edges, it means
            // that it is a leaf node that is not a terminal node and thus the tree
            // is invalid
            if(edgeList.hasNext() == false)
            {
                return false;
            }
            // loop through each child of the curNode and recurse with each of its destinationNode
            while(edgeList.hasNext())
            {
                boolean childValid = validRecurseHelper(edgeList.next().getDestinationNode());

                // if the child returned false, then the tree is false
                if(childValid == false)
                {
                    return false;
                }
            }
            // if all the child returned true, the subtree must be true
            return true;
        }
    }

    
    /**
     * Ensures that all the Terminal TreeNodes in the tree are calculated and
     * then looks
     * through each path and calculates the critical path
     * @return True on success
     *         False on failure
     *
     * @postcondition if the tree is not valid, the tree cannot have a valid
     * Critical path thus return false
     * @postcondition if the tree is valid, the tree must have a Critical path
     * thus return true
     * @todo
     */
    public boolean rollBack()
    {
        // if the tree is not valid, return false
        if(this.isValidTree() == false)
        {
            return false;
        }
        rVal = this.rollBackValuesHelper(rootNode);
        if (rVal == Double.NaN)
        {
            return false;
        }
      
        return true;
    }
   
    public double getrVal(){
    	return rVal;
    }
    /**
     * calculates each of the rollBack values for each node
     * Base Case:       
     *                  - if a terminal node does not have a terminal value, return false
     *                  - else return true
     * Recurse Case:    
     *                  - for each child recurse
     *                      - if any of them return false, return false
     *                      - cumilate a max value for each child
     *                  - set the simValue to the max value from the children. -> return true
     * @return false if a terminal node does not have a value set
     *          true on success
     */
    protected double rollBackValuesHelper(Node theCurNode)
    {
        // if the curNode is a terminalNode
        if(theCurNode instanceof TerminalNode)
        {
            return theCurNode.getRollBackValue();
        }
        // if it is a decision node, calculate the max of the rollBack value of all its children.
        // assign the max to the chance nod rollBack Value
        else if(theCurNode instanceof DecisionNode)
        {
            // cast curNode to IntermediatTreeNode
            DecisionNodeInterface curNode = (DecisionNodeInterface) theCurNode;
            Iterator<Edge> edgeList = curNode.getChildEdgeList();
            double maxRollBackValue = Double.NEGATIVE_INFINITY;
            Edge bestChildEdge = null;

            // loop through each child of the curNode and sum up their rollback value*probability
            while(edgeList.hasNext())
            {
                Edge curEdge =  edgeList.next();
                // rollBack Value of the child is the rollback value of the child * the curEdge
                double childRollbackValue = rollBackValuesHelper(curEdge.getDestinationNode());
                if(childRollbackValue > maxRollBackValue)
                {
                    maxRollBackValue = childRollbackValue;
                    bestChildEdge = curEdge;
                }
            }

            // assign the curNode with the totalRollBackValue
            curNode.setRollBackValue(maxRollBackValue);
            curNode.setBestChildEdge(bestChildEdge);
            return maxRollBackValue;
        }
        // if it is a chance node, calculate the sum of the rollBack value of all its children.
        // assign the sum to the chance nod rollBack Value
        else if(theCurNode instanceof EventNode)
        {
            // cast curNode to IntermediatTreeNode
            EventNodeInterface curNode = (EventNodeInterface) theCurNode;
            Iterator<Edge> edgeList = curNode.getChildEdgeList();
            double totalRollBackValue = 0;

            // loop through each child of the curNode and sum up their rollback value*probability
            while(edgeList.hasNext())
            {
                EventEdgeInterface curEdge = (EventEdgeInterface) edgeList.next();
                // rollBack Value of the child is the rollback value of the child * the curEdge
                totalRollBackValue = totalRollBackValue + rollBackValuesHelper(curEdge.getDestinationNode()) * curEdge.getProbability();
            }

            // assign the curNode with the totalRollBackValue
            curNode.setRollBackValue(totalRollBackValue);
            return totalRollBackValue;
        }
        return Double.NaN;
    }

    /**
     * Using the path values, it creates the critical path by placing the critical
     * treeNodes on the criticalPath list.
     * assumes that calculatePathValueHelper returns true
     *
     * BaseCase:        if the curNode is a terminalNode, end
     * Recursive Case:  for each child, find the max path
     */
//    protected void rollBackHelper(TreeNode theCurNode)
//    {
//        this.criticalPath.add(theCurNode);
//
//        // if it curNode is terminalNode end
//        if(theCurNode instanceof TerminalNodeInterface)
//        {
//            return;
//        }
//            double maxPathValue = Double.NEGATIVE_INFINITY;
//            TreeNode maxNode = null;
//
//            // cast curNode to IntermediatTreeNode
//            IntermediateTreeNode curNode = (IntermediateTreeNode) theCurNode;
//            Iterator<Edge> edgeList = curNode.getChildEdgeList();
//
//            // loop through each child of the curNode and get the max pathValue
//            while(edgeList.hasNext())
//            {
//                Edge nextEdge = edgeList.next();
//                // if the maxPathValue is not set or if the maxPathValue is less than the child's path value
//                // set the child path value as the new max
//                if(maxPathValue < nextEdge.getPathValue())
//                {
//                    maxPathValue = nextEdge.getPathValue();
//                    maxNode = nextEdge.getDestinationNode();
//                }
//            }
//            // recurse using the maxNode
//            this.rollBackHelper(maxNode);
//    }

    /**
     * Retrieve the root node of the tree.
     *
     * @return A TreeNode corresponding to the root of the DecisionTree
     * @see Node
     */
    public Node getRootNode()
    {
        return this.rootNode;
    }

    /**
     * Assigns the root node of the tree.
     *
     * @param   theRootNode : TreeNode
     * @see Node
     * @todo
     */
    public void setRootNode(Node newRootNode)
    {
    }
    @Override  
    public Object clone() throws CloneNotSupportedException {   
        return super.clone();   
    }   


    /**
     * Perform a rollback to calculate all the values for TreeNodes in the tree.
     * 
     * @postcondition if the tree is not valid the tree will not have a value
     * @postcondition if the tree is valid getTreeValue returns a value equal to the tree's expected value
     */
/*    public void performRollback()
    {
        this.rootNode.calculateNodeValue();
    }*/
   
    
    /**
     * Get the overall expected value of the entire tree.
     * 
     * @return A TreeNodevalue representing the overall value of the tree.
     * @see TreeNodeValue
     */
 /*   public TreeNodeValue getTreeValue()
    {
        return this.rootNode.getNodeValue();
    }*/
   
}
